package com.aispeech.astools.recorder;

import android.app.Application;
import android.content.Context;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import com.aispeech.astools.recorder.RecordListener.EVENT;
import com.aispeech.astools.utils.UsbCommUtils;
import com.aispeech.astools.utils.UsbPermissionRequestManager;
import com.aispeech.astools.utils.UsbPermissionRequestManager.UsbPermissionListener;
import java.lang.Thread.State;
import java.nio.ByteBuffer;
import java.util.Arrays;

/**
 * @author rain hid 中断传输
 */

public abstract class HidRecord implements BaseRecord {
    private static final String TAG = "HidRecord";

    private Application mAppContext;
    UsbManager mUsbManager;
    UsbDevice mUsbDevice;
    UsbDeviceConnection mUsbDeviceConnection;
    UsbEndpoint mUsbEndpointIn, mUsbEndpointOut;
    UsbInterface mUsbIntf;
    int mVid, mPid;

    UsbPermissionRequestManager mUsbPerManager;
    UsbRequest mUsbRequest = null;

    private HandlerThread mDataProcessThread;
    private Handler mHandler;

    boolean isReading = false;
    boolean isWorking = false;

    RecordListener mListener;

    private final int MSG_FEED_BUFFER_CMD = 1;
    private final int MSG_START_READ_CMD = 2;
    private final int MSG_STOP_READ_CMD = 3;
    private final int MSG_START_DELAY_CMD = 4;
    private final int MSG_CONNECTED_USB_CMD = 5;

    int BULK_TRANSFER_READ_SIZE = 81;
    int BULK_TRANSFER_READ_TIMEOUT = 100;

    abstract boolean startReadThread();

    public String getDevSn() {
        if (null == mUsbDeviceConnection || null == mUsbManager || null == mUsbDevice) {
            return null;
        }
        if (mUsbManager.hasPermission(mUsbDevice)) {
            Log.d(TAG, "getDevSn : get sn by connection getSerial");
            return mUsbDeviceConnection.getSerial();
        } else {
            if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
                Log.d(TAG, "getDevSn : get sn by mUsbDevice getSerialNumber");
                return mUsbDevice.getSerialNumber();
            }
        }
        return null;
    }

    int writeControlTransfer(byte[] cmdBuf, int intfIndx, int timeout) {
        Log.d(TAG, "setControlTransfer");
        if (null == mUsbDeviceConnection) {
            Log.e(TAG, "cmdControlTransfer: usb connection is null");
            return -11;
        }

        //21 09 00 02 04 00 40 00
        return mUsbDeviceConnection.controlTransfer(0x21, 0x09, 0x0200,
            intfIndx, cmdBuf, cmdBuf.length, timeout);
    }

    int readControlTransfer(byte[] data, int intfIndx, int timeout) {
        Log.d(TAG, "getControlTransfer");
        if (null == mUsbDeviceConnection) {
            Log.e(TAG, "cmdControlTransfer: usb connection is null");
            return -11;
        }
        return mUsbDeviceConnection.controlTransfer(0xa1, 0x01, 0x0100, intfIndx, data, data.length, timeout);
    }

    int readBulkTransfer(byte[] bytes, int timeout) {
        if (mUsbEndpointIn == null || mUsbDeviceConnection == null) {
            Log.w(TAG, "EndpointWrite or Connection is null ");
            return -11;
        }
        return mUsbDeviceConnection.bulkTransfer(mUsbEndpointIn, bytes, bytes.length, timeout);
    }

    int writeBulkTransfer(byte[] bytes, int timeout) {
        if (mUsbEndpointOut == null || mUsbDeviceConnection == null) {
            Log.w(TAG, "EndpointWrite or Connection is null ");
            return -11;
        }
        return mUsbDeviceConnection.bulkTransfer(mUsbEndpointOut, bytes, bytes.length, timeout);
    }


    private void createHandlerProcess() {
        Log.d(TAG, "createHandlerProcess");
        mHandler = new Handler(mDataProcessThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);

                switch (msg.what) {
//                    case MSG_FEED_BUFFER_CMD:
//                        //separate feed buffer thread and read buffer thread
//                        byte[] data = (byte[]) msg.obj;
//                        feedBuf(data);
//                        break;
                    case MSG_START_READ_CMD:
                        //remove msg otherwise make read runnable err
                        isReading = true;
                        mHandler.removeMessages(MSG_START_DELAY_CMD);
                        mHandler.removeMessages(MSG_START_READ_CMD);
                        mListener.onEvent(EVENT.START);

                        break;
                    case MSG_STOP_READ_CMD:
                        isReading = false;
                        mListener.onEvent(EVENT.STOP);
                        break;
                    case MSG_START_DELAY_CMD:
                        mHandler.removeMessages(MSG_START_READ_CMD);
                        mHandler.removeMessages(MSG_START_DELAY_CMD);

                        if (null == mUsbDevice || null == mUsbManager) {
                            Log.w(TAG, "handleMessage: null == mUsbDevice || null == mUsbManager !");
                            return;
                        }
                        if (mUsbManager.hasPermission(mUsbDevice)) {
                            if (tryToConnectUsb()) {
                                mHandler.sendEmptyMessage(MSG_START_READ_CMD);
                            }
                        } else {
                            Log.w(TAG, "handleMessage: wait permission for start read buffer,delay 2000ms");
                            mHandler.sendEmptyMessageDelayed(MSG_START_DELAY_CMD, 2000);
                        }
                        break;
                    case MSG_CONNECTED_USB_CMD:
                        synchronized (HidRecord.this) {
                            mHandler.removeMessages(MSG_CONNECTED_USB_CMD);

                            if (startReadThread()) {
                                mListener.onEvent(EVENT.START);
                            } else {
                                mListener.onError("can not start read thread ");
                            }
//                            if (mHidReadDataThread.getState().equals(State.NEW) | mHidReadDataThread.getState().equals(State.TERMINATED)) {
//                                mListener.onEvent(EVENT.INIT);
//                                mHidReadDataThread.start();
//                            } else {
//                                Log.w(TAG, "mHidReadDataThread.start failed , state not equals new or terminated : " + mHidReadDataThread.getState());
//                            }
                        }
                        break;
                }
            }
        };
    }

    public void init(Application appContext, RecordListener listener) {
        Log.d(TAG, "init: HidRecord");

        mAppContext = appContext;
        mListener = listener;
        mUsbManager = (UsbManager) appContext.getSystemService(Context.USB_SERVICE);
        mDataProcessThread = new HandlerThread("HidHandlerThread");
        mDataProcessThread.start();
        mUsbPerManager = UsbPermissionRequestManager.getInstance(appContext);
        mUsbDevice = UsbCommUtils.getUsbDevice(mAppContext, mVid, mPid);

        createHandlerProcess();
        checkUsbDevicePermission();
    }

    public void start() {
        Log.d(TAG, "startRead: " + HidRecord.this.hashCode());

        if (isReading) {
            Log.w(TAG, "start: already reading");
            return;
        }

        if (mUsbDeviceConnection == null && mUsbEndpointIn == null) {
//                tryToConnectUsb();
            //wait for permission conform
            mHandler.removeMessages(MSG_START_DELAY_CMD);
            mHandler.removeMessages(MSG_START_READ_CMD);
            mHandler.sendEmptyMessage(MSG_START_DELAY_CMD);
            return;
        }

        if (mUsbManager.hasPermission(mUsbDevice)) {
            Log.d(TAG, "startRead checkUsbDevicePermission: has permission");
            mHandler.removeMessages(MSG_STOP_READ_CMD);
            mHandler.removeMessages(MSG_START_READ_CMD);
            mHandler.sendEmptyMessage(MSG_START_READ_CMD);
        } else {
            Log.e(TAG, "startRead checkUsbDevicePermission: no permission");
        }
    }

    public void stop() {
        Log.d(TAG, "stopRead: " + this.hashCode());
        isReading = false;
    }

    public synchronized void destroy() {
        try {
            Log.d(TAG, "destroy: " + this.hashCode());
            if (isReading) {
                stop();
            }

            if (null != mListener) {
                mListener.onEvent(EVENT.DESTROY);
            }

            isWorking = false;
            if (null != mUsbRequest) {
                //request wait may block , need cancel to force return
                boolean reqRst = mUsbRequest.cancel();
                Log.w(TAG, "destroy: reqRst = " + reqRst);
                if (reqRst) {
                    //do not close here , if reqRst is true that do close in read thread
//                    mUsbRequest.close();
                } else {
                    Log.e(TAG, "destroy: " + mUsbRequest.getEndpoint());
                    Log.w(TAG, "destroy: try open again,for break request wait");
                    tryToConnectUsbForBreakRequestWait();
                }
            }

            if (null != mDataProcessThread) {
                if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
                    mDataProcessThread.getLooper().quitSafely();
                } else {
                    mDataProcessThread.getLooper().quit();
                }
            }

            if (null != mHandler) {
                mHandler.removeCallbacksAndMessages(null);
                Log.w(TAG, "destroy: mDataProcessThread : " + mDataProcessThread.getState());
            }

            //do this in HidReadDataThread tail
//            if (null != mUsbDeviceConnection) {
//                mUsbDeviceConnection.close();
//                mUsbDeviceConnection = null;
//            }
//
//            mUsbDevice = null;
//            mUsbManager = null;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void checkUsbDevicePermission() {

        if (null == mUsbDevice || null == mUsbManager) {
            Log.e(TAG, "checkUsbDevicePermission: usb device or manager is null!");
            mListener.onError("checkUsbDevicePermission: usb device or manager is null");
            return;
        }

        if (mUsbManager.hasPermission(mUsbDevice)) {
            Log.d(TAG, "checkUsbDevicePermission: has permission");
            if (!tryToConnectUsb()) {
                Log.e(TAG, "checkUsbDevicePermission: cannot connect usb device");
            }
        } else {
            Log.d(TAG, "checkUsbDevicePermission: not permission");
            mUsbPerManager.requestPermission(mUsbDevice, new UsbPermissionListener() {
                @Override
                public void onSuccess() {
                    Log.d(TAG, "onSuccess: ");
                    tryToConnectUsb();
                }

                @Override
                public void onFail(String err) {
                    Log.d(TAG, "onFail with: err = " + err + "");
                    mHandler.removeMessages(MSG_START_DELAY_CMD);
                    mListener.onError(err);
                }
            });
        }
    }

    private boolean tryToConnectUsb() {
        Log.d(TAG, "tryToConnectUsb: " + HidRecord.this.hashCode());
        if (mUsbDevice == null) {
            mListener.onError("usb device is null");
            return false;
        }

        if (isWorking) {
            Log.w(TAG, "tryToConnectUsb: already Working , return");
            return true;
        }

        Log.d(TAG, "tryToConnectUsb: get interface by 3308");
        mUsbIntf = UsbCommUtils.get3308Interface(mUsbDevice);

        if (null == mUsbIntf) {
            mUsbIntf = UsbCommUtils.getVendorInterface(mUsbDevice);
        }

        if (null == mUsbIntf) {
            Log.w(TAG, "tryToConnectUsb: get interface by standard aispeech rule");
            mUsbIntf = UsbCommUtils.getStandardInterface(mUsbDevice);//get interface by standard aispeech rule
            if (null == mUsbIntf) {
//                Log.e(TAG, "tryToConnectUsb: can not find interface : " + udev);
                return false;
            }
        }

        mUsbDeviceConnection = mUsbManager.openDevice(mUsbDevice);
        if (null == mUsbDeviceConnection) {
            Log.e(TAG, "tryToConnectUsb: can not open device : connection is null !");
            return false;
        }
        Boolean claimRet = mUsbDeviceConnection.claimInterface(mUsbIntf, true);
        Log.d(TAG, "tryToConnectUsb: claimRet = " + claimRet);
        Log.d(TAG, "tryToConnectUsb: connection getSerial = " + mUsbDeviceConnection.getSerial());

        if (claimRet) {
//            throw new EngineException(EngineError.CONNECT_USB_ERR);
            Log.d(TAG, "tryToConnectUsb: getEndpointCount = " + mUsbIntf.getEndpointCount());
            for (int i = 0; i < mUsbIntf.getEndpointCount(); i++) {
                UsbEndpoint ep = mUsbIntf.getEndpoint(i);
                // out 和in是针对Host来说的
                if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                    mUsbEndpointOut = ep;
                } else {
                    mUsbEndpointIn = ep;
                }
            }

            if (null != mUsbEndpointIn) {
                mHandler.sendEmptyMessage(MSG_CONNECTED_USB_CMD);
                return true;
            } else {
                Log.e(TAG, "tryToConnectUsb: can not get mUsbEndpointIn");
//                throw new EngineException(EngineError.CONNECT_USB_ERR);
                return false;
            }
        }
        Log.w(TAG, "tryToConnectUsb: can not Connect Usb");
        return false;
    }

    void tryToConnectUsbForBreakRequestWait() {//throws EngineException
        try {
            Log.d(TAG, "tryToConnectUsbForBreakRequestWait: " + HidRecord.this.hashCode());
            if (null != mUsbManager && null != mUsbDevice && null != mUsbIntf) {
                mUsbDeviceConnection = mUsbManager.openDevice(mUsbDevice);
                mUsbDeviceConnection.claimInterface(mUsbIntf, true);
            } else {
                Log.w(TAG, "tryToConnectUsbForBreakRequestWait: no need try,obj is null !");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
